package etrgo

import (
	"errors"
	"net/http"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

const (
	NONE = "NONE "
)

var (
	ErrHandlerNotExist = errors.New("etrgo: handler not exist")
)

type FuncMap map[string]interface{}

type ActionEntry struct {
	pattern string
	regex   *regexp.Regexp
	action  interface{}
}

type RouterConfig struct {
	Router     []*ActionEntry
	Filters    map[string]interface{}
	Loader     TemplateLoader
	StaticPath string
}

type Action struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request
}

var (
	AppRouter  *RouterConfig
	staticPath []string
)

func init() {
	if AppRouter == nil {
		AppRouter = new(RouterConfig)
		//AppRouter.Router = make(map[string]interface{})
		AppRouter.Filters = make(map[string]interface{})
		AppRouter.Router = make([]*ActionEntry, 0)
	}
}

func (conf *RouterConfig) SetTemplateDir(dir string) {
	conf.Loader.LoadDir(dir)
}

func (conf *RouterConfig) SetStaticPath(path string) {
	conf.StaticPath = path
	staticPath = strings.Split(conf.StaticPath, ",")
}

func (router *RouterConfig) AddHandler(handlers FuncMap) {
	for urlPattern, fn := range handlers {
		v := reflect.ValueOf(fn)
		if v.Kind() != reflect.Func {
			Error("value for " + urlPattern + " not a function")
			continue
		}

		re, err := regexp.Compile(urlPattern)
		if err == nil {
			entry := new(ActionEntry)
			entry.pattern = urlPattern
			entry.regex = re
			entry.action = fn
			router.Router = append(router.Router, entry)
		} else {
			Error("failed to compile the regex[%s]", urlPattern)
		}
		//router.Router[urlPattern] = fn
		Debug("add handle function[url=%s,func=%s].", urlPattern, runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
	}
}

func (router *RouterConfig) AddFilter(handlers FuncMap) {
	for name, fn := range handlers {
		v := reflect.ValueOf(fn)
		if v.Kind() != reflect.Func {
			Error("value for " + name + " not a function")
			continue
		}
		router.Filters[name] = fn
		Debug("add handle function[path=%s,func=%s].", name, v.String())
	}
}

func (router *RouterConfig) getHandler(path string) interface{} {
	for _, actionEntry := range router.Router {
		if actionEntry != nil {
			//Debug("try to match url=%s,urlPattern=%s", path, actionEntry.pattern)
			if actionEntry.regex.MatchString(path) {
				Info("path=%s,patter=%s,match", path, actionEntry.pattern)
				return actionEntry.action
			}
		} else {
			Error("item is null.")
		}
	}
	/*for handlePrefix, handler := range router.Router {
		if strings.HasPrefix(path, handlePrefix) {
			return handler
		}
	}*/
	return nil
	//return router.Router[path]
}

func (router *RouterConfig) getFilter(path string) interface{} {
	for filterPrefix, filter := range router.Filters {
		if strings.HasPrefix(path, filterPrefix) {
			return filter
		}
	}
	return nil
}

func getPath(url string) (string, bool) {
	index := strings.Index(url, "?")
	if index < 1 {
		return url, true
	}
	return url[0 : index-1], true
}

func call(fn interface{}, rw http.ResponseWriter, r *http.Request) string {
	method := reflect.ValueOf(fn)
	typ := method.Type()
	if typ.Kind() != reflect.Func {
		Error("invalid kind:%s", typ.Kind().String())
		return ""
	}
	params := make([]reflect.Value, 1)
	var action Action
	action.ResponseWriter = rw
	action.Request = r
	params[0] = reflect.ValueOf(action)
	//params[0] = reflect.ValueOf(rw)
	//params[1] = reflect.ValueOf(r)
	Debug("call the method [%s]", GetMethodName(fn))
	retValues := method.Call(params)
	if len(retValues) > 0 && retValues[0].Kind() == reflect.String {
		return retValues[0].String()
	}
	return NONE
}

func (router *RouterConfig) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	Debug("request url: %s", r.URL.Path)
	path, _ := getPath(r.URL.Path)
	r.ParseForm()
	filter := router.getFilter(path)

	if filter != nil {
		Debug("filter the request[path=%s].", path)
		call(filter, rw, r)
	}

	handler := router.getHandler(path)
	if handler != nil {
		Debug("handle the request[path=%s].", path)
		call(handler, rw, r)
		return
		//AppRouter.Loader.Render(pageView, rw, data)
	}

	if staticPath != nil && len(staticPath) > 0 {
		for _, staticDir := range staticPath {
			if strings.HasPrefix(path, staticDir) {
				curPath, _ := os.Getwd()
				file := curPath + path
				Debug("render the static file:%s", file)
				http.ServeFile(rw, r, file)
				break
			}
		}
	}
}

func (action *Action) Render(view string, data interface{}) {
	AppRouter.Loader.Render(view, action.ResponseWriter, data)
}

func (action *Action) GetInt(key string) (int64, error) {
	return strconv.ParseInt(action.Request.Form.Get(key), 10, 64)
}

func (action *Action) GetString(key string) (string, error) {
	return action.Request.Form.Get(key), nil
}
